查看原文
其他

爬取国内创业公司、投资机构、独角兽公司信息

苏克1900 高级农民工 2019-07-08

摘要: 之前爬的网站都是不需要登录就可以爬取的,但还有很多网站的内容需要先登录才能爬,比如桔子网、豆瓣、知乎等。这时采用之前的方法就不行了,需要先登录再去爬。本文以桔子网为例,介绍模拟登录方法,然后爬取该网站数据库中的数据信息,并保存到 MongoDB 数据库中。

1. 网站介绍

网址:https://www.itjuzi.com/

对于创投圈的人来说,国内的桔子和国外的 Crunchbase应该算是必备网站。今天,我们要说的就是前者,这个网站提供了很多有价值的信息。

信息的价值体现在哪里呢,举个简单的例子。你运营了一个不错的公众号,有着 10 万粉丝群,这时候你想找投资继续做大,但你不知道该到哪里去找投资。这时候你偶然发现了这个网站,在网站上你看到了同领域的大 V 号信息,他们得到了好几家公司上千万的投资。你看着心生羡慕,也跃跃欲试。于是,你经过综合对比分析,对自己公众号估值 200 万,然后就去找投资大 V 号的那几家公司洽谈。由于目的性明确,准备也充分,你很快就得到了融资。

这个例子中,桔子网提供了创业公司(运营公众号也是创业)融资金额和投资机构等相关宝贵的信息。当然,这只是非常小的一点价值,该网站数据库提供了自 2000 年以来的海量数据,包括:超过11 万家的创业公司、6 万多条投融资信息、7 千多家投资机构以及其他数据,可以说是非常全了。这些数据的背后蕴含着大量有价值的信息。

接下来,本文尝试爬取该网站数据库的信息,之后做一些数据分析,尝试找到一些有意思的信息。

主要内容:

  • 模拟登录

  • 分析 Ajax 然后抓取

  • 存储到 MongoDB 数据库

  • 导出 csv

  • 数据分析(后期)

2. 模拟登录

2.1. Session 和 Cookies

观察这个网站,是需要先登录才能看到数据信息的,但是好在不用输验证码。我们需要利用账号和密码,然后实现模拟登录。

模拟登录的方法有好几种,比如 Post 直接提交账号、先登录获取 Cookies 再直接请求、Selenium 模拟登录等。其中:

  • Post 方法需要在后台获取登录的 url,填写表单参数然后再请求,比较麻烦;

  • 直接复制 Cookies 的方法就是先登录账号,复制出 Cookies 并添加到 Headers 中,再用 requests.get 方法提交请求,这种方法最为方便;

  • Selenium 模拟登录方法是直接输入账号、密码,也比较方便,但速度会有点慢。

之后,会单独介绍几种方法的具体实现的步骤。这里,我们就先采用第二种方法。

首先,需要先了解两个知识点:Session 和 Cookies。

Session 在服务端,也就是网站的服务器,用来保存用户的会话信息,Cookies 在客户端,也可以理解为浏览器端,有了 Cookies,浏览器在下次访问网页时会自动附带上它发送给服务器,服务器通过识别 Cookies 并鉴定出是哪个用户,然后再判断用户是否是登录状态,然后返回对应的 Response。所以我们可以理解为 Cookies 里面保存了登录的凭证,有了它我们只需要在下次请求携带 Cookies 发送 Request 而不必重新输入用户名、密码等信息重新登录了。
因此在爬虫中,有时候处理需要登录才能访问的页面时,我们一般会直接将登录成功后获取的 Cookies 放在 Request Headers 里面直接请求。

更多知识,可以参考崔庆才大神的文章:

https://germey.gitbooks.io/python3webspider/content/2.4-Session%E5%92%8CCookies.html

在了解 Cookies 知识后,我们就可以进入正题了。

2.2. Requests 请求登录

首先,利用已有的账号和密码,登录进网站主页,然后右键-检查,打开第一个 www.itjuzi.com 请求:

向下拉到 Requests Headers 选项,可以看到有很多字段信息,这些信息之后我们都要添加到 Get 请求中去。

定位到下方的 Cookie 字段,可以看到有很多 Cookie 值和名称,这是在登录后自动产生的。我们将整个 Cookies 复制 Request Headers 里,然后请求网页就可以顺利登陆然后爬取。如果不加 Cookies,那么就卡在登录界面,也就无法进行后面的爬取,所以 Cookies 很重要,需要把它放到 Request Headers 里去。

下面,我们按照 json 格式开始构造 Request Headers。这里推荐一个好用的网站,可以帮我们自动构造 Request Headers:https://curl.trillworks.com/

使用方法也很简单,右键复制cURL链接到这个网页中。

将 cURL 复制到左边选框,默认选择语言为 Python,然后右侧就会自动构造后 requests 请求,包括 headers,复制下来直接可以用。登录好以后,我们就转到投融资速递网页中(url:http://radar.itjuzi.com/investevent),然后就可以获取该页面网页内容了。代码如下:

1import requests
2headers = {
3    'Connection''keep-alive',
4    'Cache-Control''max-age=0',
5    'Upgrade-Insecure-Requests''1',
6    'User-Agent''Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36',
7    'Accept''text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
8    'DNT''1',
9    'Referer''http://radar.itjuzi.com/investevent',
10    'Accept-Encoding''gzip, deflate, br',
11    'Accept-Language''zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7',
12    'If-None-Match''W/^\\^5bc7df15-19cdc^\\^',
13    'If-Modified-Since''Thu, 18 Oct 2018 01:17:09 GMT',
14    # 主页cookie
15    'Cookie''你的cookie',
16    }
17
18url = 'http://radar.itjuzi.com/investevent'   # 投融资信息
19s = requests.Session()
20response = s.get(url,headers = headers)
21print(response.status_code)
22print(response.text)

结果如下:

可以看到,添加 Cookie 后,我们请求投融资信息网页就成功了。这里如果不加 Cookie 的结果就什么也得不到:

好,这样就算成功登录了。但是整个 headers 请求头的参数太多,是否一定需要带这么多参数呢? 这里就去尝试看哪些参数是请求一定要的,哪些则是不用的,不用的可以去掉以精简代码。经过尝试,仅需要下面三个参数就能请求成功。

1headers = {
2    'User-Agent''Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
3    'X-Requested-With''XMLHttpRequest',
4    # 主页cookie
5    'Cookie''复制你的cookie',
6    }

Tips:当爬取失效的时候需要重新注册帐号,然后生成新的 Cookie。

如果你没那么多邮箱账号,那么推荐一个可生成随机账号的免费邮箱,用来接收注册激活链接:

https://10minutemail.net/

3. 网页爬取分析

在成功登录以后,我们就可以通过分析网页结构来采取相应的爬取方法。这里,我们将爬取投融资速递、创业公司、投资机构和千里马等几个子板块的数据。首先,以投融资速递信息为例。

网址:http://radar.itjuzi.com/investevent

3.1. 分析网页

可以看到,投融资事件信息网页中的数据是表格形式。经尝试点击翻页,发现url不变,可以初步判定网页数据采用 Ajax 形式呈现。切换到后台,点击翻页可以发现出现有规律的 info?locatiop 开头的请求,页数随 page 参数而发生规律的变化。

点击请求查分别查看 Response 和 Preview,发现表格数据是 json 格式,这就太好了。因为 json 格式的数据非常整齐也很容易抓取。上一篇文章,我们正好解决了 json 格式数据的处理方法,如果不太熟悉可以回顾一下:

https://www.makcyun.top/web_scraping_withpython6.html

接着,我们就可以通过构造 url 参数,然后用 Get 请求就可以获取网页中的表格数据,最后再加个循环,就可以爬取多页数据了。

3.2. 构造 url

下面,我们来构造一下 url,切换到 Headers 选项卡,拉到最底部可以看到 url 需带的请求参数。这里有三项,很好理解。location:in,表示国内数据; orderby:def,表示默认排序;page:1,表示第一页。所以,只需要改变 page 参数就可以查看其他页的结果,非常简单。

这里,如果我们对表格进行筛选,比如行业选择教育、时间选择 2018 年,那么相应的请求参数也会增加。通过构造参数就可以爬取指定的数据,这样就不用全部爬下来了,也对网站友好点。

3.3. 爬取数据

到这儿,我们就可以直接开始爬了。可以使用函数,也可以用定义类(Class)的方法。考虑到,Python 是一种面向对象的编程,类(Class)是面向对象最重要的概念之一,运用类的思想编程非常重要。所以,这里我们尝试采用类的方法来实现爬虫。

1import requests
2import pymongo
3import random
4import time
5import json
6import pandas as pd
7from fake_useragent import UserAgent
8ua = UserAgent()
9
10class ITjuzi(object):
11    def __init__(self):
12        self.headers = {
13            'User-Agent': ua.random,
14            'X-Requested-With''XMLHttpRequest',
15            'Cookie''你的cookie',
16        }
17        self.url = 'http://radar.itjuzi.com/investevent/info?'    # investevent
18        self.session = requests.Session()
19
20    def get_table(self, page):
21        """
22        1 获取投融资事件数据
23        """

24        params = {                # invsestevent
25            'location''in',
26            'orderby''def',
27            'page': page,
28            'date'2018  # 年份
29        }
30        response = self.session.get(
31            self.url, params=params, headers=self.headers).json()
32        print(response)
33        # self.save_to_mongo(response)
34
35if __name__ == '__main__':
36    spider = itjuzi()
37    spider.get_table(1)

如果你之前一直是用 Def 函数的写法,而没有接触过 Class 类的写法,可能会看地比较别扭,我之前就是这样的,搞不懂为什么要有 self为什么用__init__。这种思维的转变可以通过看教程和别人写的实际案例去揣摩。这里,我先略过,之后会单独介绍。

简单解释一下上面代码的意思。首先定义了一个类(class),类名是 ITjuzi,类名通常是大写开头的单词。后面的 (object)  表示该类是从哪个类继承下来的,这个可以暂时不用管,填上就可以。然后定义了一个特殊的__init__方法,__init__方法的第一个参数永远是 self,之后是其他想要设置的属性参数。在这个方法里可以绑定些确定而且必须的属性,比如 headers、url 等。

在 headers 里面,User-Agent 没有使用固定的 UA,而是借助 fake_useragent包 生成随机的 UA:ua.random。因为,这个网站做了一定的反爬措施,这样可以起到一定的反爬效果,后续我们会再说到。接着,定义了一个 get_table() 函数,这个函数和普通函数没有什么区别,除了第一个参数永远是实例变量 self。在 session.get()方法中传入 url、请求参数和 headers,请求网页并指定获取的数据类型为 json 格式,然后就可以顺利输出 2018 年投融资信息的第 1 页数据:

3.4. 存储到 MongoDB

数据爬取下来了,那么我们放到哪里呢?可以选择存储到 csv 中,但 json 数据中存在多层嵌套,csv 不能够直观展现。这里,我们可以尝试之前没有用过的 MongoDB 数据库,当作练习。另外,数据存储到该数据库中,后期也可以导出 csv,一举两得。

关于 MongoDB 的安装与基本使用,可以参考下面这两篇教程,之后我也会单独再写一下:

安装

https://germey.gitbooks.io/python3webspider/content/1.4.2-MongoDB%E7%9A%84%E5%AE%89%E8%A3%85.html

使用

https://germey.gitbooks.io/python3webspider/content/5.3.1-MongoDB%E5%AD%98%E5%82%A8.html

可视化工具可采用 Robo 3T (之前叫 RoboMongo)

https://www.mongodb.com/

下面我们就将上面返回的 json 数据,存储到 MongoDB 中去:

1import pymongo
2import numpy as np
3# mongodb数据库初始化
4client = pymongo.MongoClient('localhost'27017)
5# 指定数据库
6db = client.ITjuzi
7# 指定集合,类似于mysql中的表
8mongo_collection1 = db.itjuzi_investevent
9
10def save_to_mongo(self, response):
11        try:
12            data = response['data']['rows']  # dict可以连续选取字典层内的内容
13            df = pd.DataFrame(data)
14            table = json.loads(df.T.to_json()).values()
15            if mongo_collection1.insert_many(table):  # investment
16                print('存储到mongodb成功')
17                sleep = np.random.randint(37)
18                time.sleep(sleep)
19        except Exception:
20            print('存储到mongodb失败')
21 def spider_itjuzi(self, start_page, end_page):
22        for page in range(start_page, end_page):
23            print('下载第%s页:' % (page))
24            self.get_table(page)
25        print('下载完成')
26
27if __name__ == '__main__':
28    spider = ITjuzi()
29    spider.spider_itjuzi(12)

这里,安装好 MongoingDB 数据库、Robo 3T 和 pymongo 库后,我们就可以开始使用了。

首先,对数据库进行初始化,然后指定(如果没有则生成)数据将要存放的数据库和集合名称。接着,定义了save_to_mongo 函数。由于表格里面的数据存储在键为 rows 的 value 值中,可使用 response['data']['rows'] 来获取到 json 里面的嵌套数据,然后转为 DataFrame。DataFrame 存储 MongoDB 参考了 stackoverflow 上面的一个答案:json.loads(df.T.to_json()).values()

然后,使用 mongo_collection1.insert_many(table) 方法将数据插入到 mongo_collection1,也就是 itjuzi_investevent 集合中。爬取完一页数据后,设置随机延时 3-6 s,避免爬取太频繁,这也能起到一定的反爬作用。

最后,我们定义一个分页循环爬取函数 spider_itjuzi,利用 for 循环设置爬取起始页数就可以了,爬取结果如下:

打开 Robo 3T,可以看到数据成功存储到 MongoDB 中了:

好,以上,我们就基本上完成了 2018 年投融资信息数据表的爬取,如果你想爬其他年份或者更多页的数据,更改相应的参数即可。

3.5. 导出到 csv

数据存好后,如果还不太熟悉 MongoDB 的对数据的操作,那么我们可以将数据导出为 csv,在 excel 中操作。MongoDB不能直接导出 csv,但操作起来也不麻烦,利用mongoexport命令,几行代码就可以输出 csv。

mongoexport导出 csv 的方法:

https://docs.mongodb.com/manual/reference/program/mongoexport/#mongoexport-fields-example

首先,运行 cmd,切换路径到 MongoDB 安装文件夹中的 bin 目录下,我这里是:

1cd C:\Program Files\MongoDB\Server\4.0\bin

接着,在桌面新建一个txt文件,命名为fields,在里面输入我们需要输出的表格列名,如下所示:

然后,利用mongoexport命令,按照:表格所在的数据库、集合、输出格式、导出列名文件位置和输出文件名的格式,编写好命令并运行就可以导出了:

1mongoexport --db ITjuzi --collection itjuzi_investevent --type=csv --fieldFile C:\Users\sony\Desktop\fields.txt --out C:\Users\sony\Desktop\investevent.csv 

cmd 命令:

导出 csv 结果如下:

Tips:直接用 excel 打开可能会是乱码,需先用 Notepad++ 转换为 UTF-8 编码,然后 excel 再打开就正常了。

以上,我们就完成了整个数据表的爬取。

3.6. 完整代码

下面,可以再尝试爬取创业公司、投资机构和千里马的数据。他们的数据结构形式是一样的,只需要更换相应的参数就可以了,感兴趣的话可以尝试下。将上面的代码再稍微整理一下,完整代码如下:

 1import requests
 2import re
 3import pymongo
 4import random
 5import time
 6import json
 7import random
 8import numpy as np
 9import csv
10import pandas as pd
11from fake_useragent import UserAgent
12import socket  # 断线重试
13from urllib.parse import urlencode
14# 随机ua
15ua = UserAgent()
16# mongodb数据库初始化
17client = pymongo.MongoClient('localhost'27017)
18# 获得数据库
19db = client.ITjuzi
20# 获得集合
21mongo_collection1 = db.itjuzi_investevent
22mongo_collection2 = db.itjuzi_company
23mongo_collection3 = db.itjuzi_investment
24mongo_collection4 = db.itjuzi_horse
25
26class itjuzi(object):
27    def __init__(self):
28
29        self.headers = {
30            'User-Agent': ua.random,
31            'X-Requested-With''XMLHttpRequest',
32            # 主页cookie
33            'Cookie''你的cookie',
34        }
35        self.url = 'http://radar.itjuzi.com/investevent/info?'    # investevent
36        # self.url = 'http://radar.itjuzi.com/company/infonew?'       # company
37        # self.url = 'http://radar.itjuzi.com/investment/info?'       # investment
38        # self.url = 'https://www.itjuzi.com/horse'               # horse
39        self.session = requests.Session()
40
41    def get_table(self, page):
42        """
43        1 获取投融资事件网页内容
44        """

45        params = {                # 1 invsestevent
46        'location''in',
47        'orderby''def',
48        'page': page,
49        'date':2018  #年份  
50        }
51
52        # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # #
53        # params = {                  # 2 company
54        #     'page': page,
55        #     # 'scope[]': 1,  # 行业 1教育
56        #     'orderby': 'pv',
57        #     'born_year[]': 2018,  # 只能单年,不能多年筛选,会保留最后一个
58        # }
59        # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # #
60        # params = {                  # 3 investment
61        # 'orderby': 'num',
62        # 'page': page
63        # }
64        # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # # # # #
65        # params = {                  # 4 horse
66        # }
67        # 可能会遇到请求失败,则设置3次重新请求
68        retrytimes = 3
69        while retrytimes:
70            try:
71                response = self.session.get(
72                    self.url, params=params, headers=self.headers,timeout = (5,20)).json()
73                self.save_to_mongo(response)
74                break
75            except socket.timeout:
76                print('下载第{}页,第{}次网页请求超时' .format(page,retrytimes))
77                retrytimes -=1
78
79    def save_to_mongo(self, response):
80        try:
81            data = response['data']['rows']  # dict可以连续选取字典层内的内容
82            # data =response  # 爬取千里马时需替换为此data
83            df = pd.DataFrame(data)
84            table = json.loads(df.T.to_json()).values()
85            if mongo_collection1.insert_many(table):      # investevent
86            # if mongo_collection2.insert_many(table):    # company
87            # if mongo_collection3.insert_many(table):    # investment
88            # if mongo_collection4.insert_many(table):    # horse 
89                print('存储到mongodb成功')
90                sleep = np.random.randint(37)
91                time.sleep(sleep)
92        except Exception:
93            print('存储到mongodb失败')
94
95    def spider_itjuzi(self, start_page, end_page):
96        for page in range(start_page, end_page):
97            print('下载第%s页:' % (page))
98            self.get_table(page)
99        print('下载完成')
100
101if __name__ == '__main__':
102    spider = itjuzi()
103    spider.spider_itjuzi(12)   

源代码也可以在下面的链接中获取:

https://github.com/makcyun/web_scraping_with_python

4. 总结:

  • 本文以桔子网为例,介绍了需登录网站的爬取方法。即:先模拟登录再爬取数据信息。但是还有一些网站登录时需要输入验证码,这让爬取难度又增加,后期会再进行介绍。

  • 该网站相比之前的爬虫网站,反爬措施高了很多。本文通过设置随机延时、随机 UserAgent,可一定程度上增加爬虫的稳定性。但是仍然会受到反爬措施的限制,后期可尝试通过设置 IP 代理池进一步提升爬虫效率。

  • 上面的爬虫程序在爬取过程容易中断,接着再进行爬取即可。但是手动修改非常不方便,也容易造成数据重复爬取或者漏爬。所以,为了更完整地爬取,需增加断点续传的功能。

补充一下,后期可能会在我的博客中不断更新、补充本文的内容。如想获得更多,可点击阅读原文,或者浏览器打开我的博客链接。
https://www.makcyun.top

本文完。

欢迎长按关注我的微信公众号



Modified on

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存